/**
 * 
 */
package cz.vutbr.web.csskit.antlr;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.Charset;
import java.util.zip.GZIPInputStream;

import org.antlr.runtime.ANTLRInputStream;
import org.antlr.runtime.ANTLRReaderStream;
import org.antlr.runtime.CharStream;

/**
 * Wraps ANTLR stream with useful decorations, mainly to allow switching
 * encoding on lexer
 * 
 * @author kapy
 * 
 */
public class CSSInputStream implements CharStream {

    /**
     * ANTLR input
     */
    private CharStream input;

    /**
     * Raw data of string passed, if any
     */
    private String rawData;

    /**
     * Base location of this input stream
     */
    private URL base = null;

    /**
     * Source URL for URL streams, null for string streams
     */
    private URL url;

    /**
     * Source input stream for URL streams, null for string streams
     */
    private InputStream source = null;

    /**
     * Encoding of file or string. If <code>null</code>
     */
    private String encoding;

    public static CSSInputStream stringStream(String source) throws IOException {
        CSSInputStream stream = new CSSInputStream();
        if (source == null) {
            return stream;
        }
        stream.rawData = source;
        stream.encoding = Charset.defaultCharset().name();

        BufferedReader br = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(source.getBytes()),
                stream.encoding));
        stream.input = new ANTLRReaderStream(br);

        return stream;
    }

    public static CSSInputStream urlStream(URL source, String encoding) throws IOException {
        CSSInputStream stream = new CSSInputStream();

        stream.base = source;
        if (encoding != null)
            stream.encoding = encoding;
        else
            stream.encoding = Charset.defaultCharset().name();

        URLConnection con = source.openConnection();
        con.setRequestProperty("User-Agent",
                "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.4; en-US; rv:1.9.2.2) Gecko/20100316 Firefox/3.6.2");
        InputStream is;
        if ("gzip".equalsIgnoreCase(con.getContentEncoding()))
            is = new GZIPInputStream(con.getInputStream());
        else
            is = con.getInputStream();
        stream.input = new ANTLRInputStream(is, stream.encoding);
        stream.source = is;
        stream.url = source;

        return stream;
    }

    // Sole constructor
    // force using factory methods
    private CSSInputStream() {
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.antlr.runtime.CharStream#LT(int)
     */
    @Override
    public int LT(int arg0) {
        return input.LT(arg0);
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.antlr.runtime.CharStream#getCharPositionInLine()
     */
    @Override
    public int getCharPositionInLine() {
        return input.getCharPositionInLine();
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.antlr.runtime.CharStream#getLine()
     */
    @Override
    public int getLine() {
        return input.getLine();
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.antlr.runtime.CharStream#setCharPositionInLine(int)
     */
    @Override
    public void setCharPositionInLine(int arg0) {
        input.setCharPositionInLine(arg0);
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.antlr.runtime.CharStream#setLine(int)
     */
    @Override
    public void setLine(int arg0) {
        input.setLine(arg0);
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.antlr.runtime.CharStream#substring(int, int)
     */
    @Override
    public String substring(int arg0, int arg1) {
        return input.substring(arg0, arg1);
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.antlr.runtime.IntStream#LA(int)
     */
    @Override
    public int LA(int arg0) {
        return input.LA(arg0);
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.antlr.runtime.IntStream#consume()
     */
    @Override
    public void consume() {
        input.consume();
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.antlr.runtime.IntStream#index()
     */
    @Override
    public int index() {
        return input.index();
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.antlr.runtime.IntStream#mark()
     */
    @Override
    public int mark() {
        return input.mark();
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.antlr.runtime.IntStream#release(int)
     */
    @Override
    public void release(int arg0) {
        input.release(arg0);
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.antlr.runtime.IntStream#rewind()
     */
    @Override
    public void rewind() {
        input.rewind();
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.antlr.runtime.IntStream#rewind(int)
     */
    @Override
    public void rewind(int arg0) {
        input.rewind(arg0);
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.antlr.runtime.IntStream#seek(int)
     */
    @Override
    public void seek(int arg0) {
        input.seek(arg0);
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.antlr.runtime.IntStream#size()
     */
    @Override
    public int size() {
        return input.size();
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.antlr.runtime.CharStream#getSourceName()
     */
    @Override
    public String getSourceName() {
        return base != null ? base.toString() : "";
    }

    /**
     * Obtains the current base URL used for locating the eventual imported
     * style sheets.
     * 
     * @return The base URL.
     */
    public URL getBase() {
        return base;
    }

    /**
     * Sets the base URL used for locating the eventual imported style sheets.
     * 
     * @param base
     *            The new base URL.
     */
    public void setBase(URL base) {
        this.base = base;
    }

    /**
     * Obtains current character encoding used for processing the style sheets.
     * 
     * @return The charset name.
     */
    public String getEncoding() {
        return encoding;
    }

    /**
     * Sets a new encoding for the input stream. <b>Warning:</b> this resets the
     * stream i.e. a new connection is opened and all the data is read again.
     * 
     * @param enc
     *            The new encoding name.
     * @throws IOException
     */
    public void setEncoding(String enc) throws IOException {
        if (source != null) // applicapble to URL streams only
        {
            String current = encoding;
            if (current == null)
                current = Charset.defaultCharset().name();
            if (!current.equalsIgnoreCase(enc)) {
                source.close();
                encoding = enc;
                CSSInputStream newstream = urlStream(url, encoding);
                input = newstream.input;
            }
        }
    }

    /**
     * 
     * @return the raw data
     */
    public String getRawData() {
        return rawData;
    }

}
